/**
 * @license
 * Copyright 2018 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Gulp script to build Blockly for Node & NPM.
 */

var gulp = require('gulp');
gulp.replace = require('gulp-replace');
gulp.rename = require('gulp-rename');
gulp.sourcemaps = require('gulp-sourcemaps');

var closureCompiler = require('google-closure-compiler').gulp();
var argv = require('yargs').argv;

////////////////////////////////////////////////////////////
//                        Build                           //
////////////////////////////////////////////////////////////

const licenseRegex = `\\/\\*\\*
 \\* @license
 \\* (Copyright \\d+ (Google LLC|Massachusetts Institute of Technology))
( \\* All rights reserved.
)? \\* SPDX-License-Identifier: Apache-2.0
 \\*\\/`;

/**
 * Helper method for stripping the Google's and MIT's Apache Licenses.
 */
function stripApacheLicense() {
  // Strip out Google's and MIT's Apache licences.
  // Closure Compiler preserves dozens of Apache licences in the Blockly code.
  // Remove these if they belong to Google or MIT.
  // MIT's permission to do this is logged in Blockly issue #2412.
  return gulp.replace(new RegExp(licenseRegex, "g"), '\n\n\n\n');
  // Replace with the same number of lines so that source-maps are not affected.
}

/**
 * Closure compiler warning groups used to treat warnings as errors.
 * For a full list of closure compiler groups, consult:
 * https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/DiagnosticGroups.java#L113
 */
var JSCOMP_ERROR = [
  'accessControls',
  'checkPrototypalTypes',
  'checkRegExp',
  'checkTypes',
  'checkVars',
  'conformanceViolations',
  'const',
  'constantProperty',
  'deprecated',
  'deprecatedAnnotations',
  'duplicateMessage',
  'es5Strict',
  'externsValidation',
  'functionParams',
  'globalThis',
  'invalidCasts',
  'misplacedTypeAnnotation',
  'missingGetCssName',
  // 'missingOverride',
  'missingPolyfill',
  'missingProperties',
  'missingProvide',
  'missingRequire',
  'missingReturn',
  // 'missingSourcesWarnings',
  'moduleLoad',
  'msgDescriptions',
  'nonStandardJsDocs',
  // 'polymer',
  // 'reportUnknownTypes',
  // 'strictCheckTypes',
  // 'strictMissingProperties',
  'strictModuleDepCheck',
  // 'strictPrimitiveOperators',
  'suspiciousCode',
  'typeInvalidation',
  'undefinedNames',
  'undefinedVars',
  'underscore',
  'unknownDefines',
  'unusedLocalVariables',
  'unusedPrivateMembers',
  'useOfGoogBase',
  'uselessCode',
  'untranspilableFeatures',
  'visibility'
];

/**
 * Helper method for calling the Closure compiler.
 * @param {*} compilerOptions
 * @param {boolean=} opt_verbose Optional option for verbose logging
 * @param {boolean=} opt_warnings_as_error Optional option for treating warnings
 *     as errors.
 */
function compile(compilerOptions, opt_verbose, opt_warnings_as_error) {
  compilerOptions = compilerOptions || {};
  compilerOptions.compilation_level = 'SIMPLE_OPTIMIZATIONS';
  compilerOptions.warning_level = opt_verbose ? 'VERBOSE' : 'DEFAULT';
  compilerOptions.language_in =
    compilerOptions.language_in || 'ECMASCRIPT5_STRICT';
  compilerOptions.language_out = 'ECMASCRIPT5_STRICT';
  compilerOptions.rewrite_polyfills = false;
  compilerOptions.hide_warnings_for = 'node_modules';
  if (opt_warnings_as_error) {
    compilerOptions.jscomp_error = JSCOMP_ERROR;
  }

  const platform = ['native', 'java', 'javascript'];

  return closureCompiler(compilerOptions, { platform });
}

/**
 * A helper method to return an closure compiler output wrapper that wraps the
 * body in a Universal Module Definition.
 * @param {string} namespace The export namespace.
 * @param {Array.<Object>} dependencies An array of dependencies to inject.
 */
function outputWrapperUMD(namespace, dependencies) {
  const amdDeps = dependencies.map(d => '\'' + d.amd + '\'' ).join(', ');
  const cjsDeps = dependencies.map(d => `require('${d.cjs}')`).join(', ');
  const browserDeps = dependencies.map(d => 'root.' + d.name).join(', ');
  const imports = dependencies.map(d => d.name).join(', ');
  return `// Do not edit this file; automatically generated by gulp.

/* eslint-disable */
;(function(root, factory) {
  if (typeof define === 'function' && define.amd) { // AMD
    define([${amdDeps}], factory);
  } else if (typeof exports === 'object') { // Node.js
    module.exports = factory(${cjsDeps});
  } else { // Browser
    root.${namespace} = factory(${browserDeps});
  }
}(this, function(${imports}) {
  %output%
return ${namespace};
}));
`;
};

/**
 * This task builds the Blockly's built in blocks.
 *     blocks_compressed.js
 */
function buildBlocks() {
  return gulp.src(['blocks/*.js'], {base: './'})
    .pipe(stripApacheLicense())
    .pipe(gulp.sourcemaps.init())
    .pipe(compile({
      dependency_mode: 'NONE',
      externs: ['./externs/goog-externs.js', './externs/block-externs.js'],
      js_output_file: 'blocks_compressed.js',
      output_wrapper: outputWrapperUMD('Blockly.Blocks', [{
        name: 'Blockly',
        amd: './blockly_compressed.js',
        cjs: './blockly_compressed.js'
      }])
    }, argv.verbose, argv.strict))
    .pipe(gulp.sourcemaps.write('.', {
      includeContent: false,
      sourceRoot: './'
    }))
    .pipe(gulp.dest('./BlockPi/src/script/'));
};

gulp.task('build-blocks', buildBlocks);

/**
 * A helper method for building a Blockly code generator.
 * @param {string} language Generator language.
 * @param {string} namespace Language namespace.
 */
function buildGenerator(language, namespace) {
  return gulp.src([`generators/${language}.js`, `generators/${language}/*.js`], {base: './'})
    .pipe(stripApacheLicense())
    .pipe(gulp.sourcemaps.init())
    .pipe(compile({
      dependency_mode: 'NONE',
      externs: ['./externs/goog-externs.js', './externs/generator-externs.js'],
      js_output_file: `${language}_compressed.js`,
      output_wrapper: outputWrapperUMD(`Blockly.${namespace}`, [{
        name: 'Blockly',
        amd: './blockly_compressed.js',
        cjs: './blockly_compressed.js'
      }])
    }, argv.verbose, argv.strict))
    .pipe(gulp.sourcemaps.write('.', {
      includeContent: false,
      sourceRoot: './'
    }))
    .pipe(gulp.dest('./BlockPi/src/script/'));
};


/**
 * This task builds the python generator.
 *     python_compressed.js
 */
gulp.task('build-python', function() {
  return buildGenerator('python', 'Python');
});
